/***********************************************************
 *  Copyright (C) 2023 by JakodYuan (JakodYuan@outlook.com).
 *  All right reserved.
************************************************************/
`ifndef rv_jtag__SV
`define rv_jtag__SV

`ifndef rv_jtag_DATA_WIDTH
`define rv_jtag_DATA_WIDTH 1024
`endif

`ifndef rv_jtag_ADDR_WIDTH
`define rv_jtag_ADDR_WIDTH 128
`endif


class rv_jtag  extends uvm_object;
    `uvm_object_utils(rv_jtag)

    virtual rv_jtag_if vif;

    local rv_jtag me;

    extern function new(string name="rv_jtag");
    extern task ir_scan(input  int                                width,
                        input  logic [`rv_jtag_DATA_WIDTH-1:0]   tdi,
                        output logic [`rv_jtag_ADDR_WIDTH-1:0]   tdo);

    extern task dr_scan(input  int                                width,
                        input  logic [`rv_jtag_DATA_WIDTH-1:0]   tdi,
                        output logic [`rv_jtag_ADDR_WIDTH-1:0]   tdo);

    extern task drive_idle();
    extern task tms_rst(int cycle_num=5);
    extern task trst_rst(int cycle_num=5);

    extern task read_DP_reg(input  logic [1:0]  addr,
                            output logic [31:0] data);

    extern task write_DP_reg(input logic [1:0]  addr,
                             input logic [31:0] data);

    extern task read_DM_reg(input  logic [31:0] addr,
                            output logic [31:0] data);

    extern task write_DM_reg(input logic [31:0] addr,
                             input logic [31:0] data);
endclass

function rv_jtag::new(string name="rv_jtag");
    super.new(name);
endfunction


task rv_jtag::ir_scan(input  int                                width,
                       input  logic [`rv_jtag_DATA_WIDTH-1:0]   tdi,
                       output logic [`rv_jtag_ADDR_WIDTH-1:0]   tdo);
    logic tmp;

    tdo = 0;
    vif.drive_jtag(1, 0, tmp);
    vif.drive_jtag(1, 0, tmp);
    vif.drive_jtag(0, 0, tmp);
    vif.drive_jtag(0, 0, tmp);

    for(int i = 0; i < width-1; ++i) begin 
        vif.drive_jtag(0, tdi[i], tdo[i]);
    end

    vif.drive_jtag(1, tdi[width-1], tdo[width-1]);
    vif.drive_jtag(1, 0, tdo[width]);
    vif.drive_jtag(0, 0, tmp);

endtask

task rv_jtag::dr_scan(input  int                                width,
                       input  logic [`rv_jtag_DATA_WIDTH-1:0]   tdi,
                       output logic [`rv_jtag_ADDR_WIDTH-1:0]   tdo);
    logic tmp;

    tdo = 0;
    vif.drive_jtag(1, 0, tmp);
    vif.drive_jtag(0, 0, tmp);
    vif.drive_jtag(0, 0, tmp);

    for(int i = 0; i < width-1; ++i) begin
        vif.drive_jtag(0, tdi[i], tdo[i]);
    end

    vif.drive_jtag(1, tdi[width-1], tdo[width-1]);
    vif.drive_jtag(1, 0, tdo[width]);
    vif.drive_jtag(0, 0, tmp);

endtask

task rv_jtag::drive_idle();
    logic tmp;

    vif.drive_jtag(1, 0, tmp);
endtask

task rv_jtag::tms_rst(int cycle_num=5);
    logic tmp;
    for(int i = 0; i < cycle_num; ++i)
        vif.drive_jtag(1, 0, tmp);

    vif.drive_jtag(0, 0, tmp);
endtask


task rv_jtag::trst_rst(int cycle_num=5);
    vif.reset(cycle_num);
endtask




task rv_jtag::read_DP_reg(input  logic [1:0]  addr,
                           output logic [31:0] data);
    bit [40:0] tdi;
    bit [41:0] tdo;

    forever begin
        ir_scan(5, 'h11, tdo);
        tdi[33:2] = data;
        tdi[40:34]  = addr;
        tdi[1:0]    = 2'b01;
        dr_scan(41, tdi, tdo);
        tdo >>= 1;
        if(tdo[2:0] != 1) break;
    end
    data = tdo[33:2];
endtask

task rv_jtag::write_DP_reg(input logic [6:0]  addr,
                            input logic [31:0] data);
    bit [40:0] tdi;
    bit [41:0] tdo;

    forever begin
        ir_scan(5, 'h11, tdo);
        tdi[33:2] = data;
        tdi[40:34]  = addr;
        tdi[1:0]    = 2'b10;
        dr_scan(41, tdi, tdo);
        tdo >>= 1;
        if(tdo[2:0] != 1) break;
    end

endtask


task rv_jtag::read_DM_reg(input  logic [6:0] addr,
                           output logic [31:0] data);
    read_DP_reg(addr, data);
    tms_rst();
    read_DP_reg(addr, data);
endtask

task rv_jtag::write_DM_reg(input logic [6:0] addr,
                            input logic [31:0] data);
    write_DP_reg(addr, data);

endtask


`endif
